﻿#region Using's

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Threading;

#endregion

[assembly:
	SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils",
		Scope = "namespace", Target = "MyUtils")]

namespace MyUtils
{
	[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils")]
	public static partial class Utils
	{
		static readonly WeakKeyCache<object, DictionaryObject> _attachedDictionaryContainer = new WeakKeyCache<object, DictionaryObject>();

		public static DictionaryObject AttachedDictionary(this object item)
		{
			return _attachedDictionaryContainer[item, () => new DictionaryObject()];
		}


		public static IAsyncResult DisposeAsync(this IDisposable disposable)
		{
			if (disposable != null)
			{
				return new Action<IDisposable>(x => x.Dispose()).BeginInvoke(disposable, null, null);
			}
			return null;
		}

		public static void WaitAll(this IEnumerable<IAsyncResult> asyncResults)
		{
			if (asyncResults != null)
			{
				var handlesArray = asyncResults.Select(x => x.AsyncWaitHandle).ToArray();

//				for (int i = 0; i < handlesArray.Length; i += 64)
//				{
//					var m = Math.Min(64, handlesArray.Length - i);
//					var arr = new WaitHandle[m];
//					Array.Copy(handlesArray, i, arr, 0, m);
//					new Action(delegate
//					{
//						WaitHandle.WaitAll(arr);
//					}).BeginInvoke(null, null).AsyncWaitHandle.WaitOne();
//				}

				foreach (var waitHandle in handlesArray)
				{
					waitHandle.WaitOne();
				}
			}
		}

//		public static int CountOfNbsp(this string str)
//		{
//			return CountOfChar(str, 160);
//		}
//
//		public static int CountOfChar(this string str, int chr)
//		{
//			return str.Where(x => x == (char)chr).Count();
//		}

		public static int ToInt(this byte[] bytes)
		{
			(bytes.Length == 4).Ensure();
			if (!BitConverter.IsLittleEndian) // for porting to some platforms, like MONO
			{
				bytes = (byte[]) bytes.Clone();
				Array.Reverse(bytes);
			}
			return BitConverter.ToInt32(bytes, 0);
		}

		//		public static ushort ToUShort(this byte[] bytes)
		//		{
		//			(bytes.Length == 2).Ensure();
		//			return (ushort)(bytes[0] + (bytes[1] << 8));
		//if (!BitConverter.IsLittleEndian) // for porting to some platforms, like MONO
		//{
		//    bytes = (byte[])bytes.Clone();
		//    Array.Reverse(bytes);
		//}
		//return BitConverter.ToUInt16(bytes, 0);
		//		}

		public static byte[] ToBytes(this int num)
		{
			var intBytes = BitConverter.GetBytes(num);
			if (!BitConverter.IsLittleEndian) // for porting to some platforms, like MONO
			{
				Array.Reverse(intBytes);
			}
			(intBytes.Length == 4).Ensure();
			return intBytes;
		}

		//		public static byte[] ToBytes(this ushort num)
		//		{
		//			return new[] { (byte)(num % 256), (byte)(num >> 8) };
		//var intBytes = BitConverter.GetBytes(num);
		//if (!BitConverter.IsLittleEndian) // for porting to some platforms, like MONO
		//{
		//    Array.Reverse(intBytes);
		//}
		//(intBytes.Length == 4).Ensure();
		//return intBytes;
		//		}

		const int _defaultPoolingIntervalMs = 330;
		const int _defaultMaxTimeMs = 5000;

		public static void WaitForEx(this Func<bool> condition)
		{
			WaitForEx(condition, _defaultMaxTimeMs, _defaultPoolingIntervalMs);
		}

		public static void WaitForEx(this Func<bool> condition, int maxTimeMs)
		{
			WaitForEx(condition, maxTimeMs, _defaultPoolingIntervalMs);
		}

		public static void WaitForEx(this Func<bool> condition, int maxTimeMs = _defaultMaxTimeMs, int poolingIntervalMs = _defaultPoolingIntervalMs)
		{
			WaitForEx<object>(x => condition(), null, maxTimeMs, poolingIntervalMs);
		}

		public static void WaitForEx<T>(this Func<T, bool> condition, T arg, int maxTimeMs = _defaultMaxTimeMs, int poolingIntervalMs = _defaultPoolingIntervalMs)
		{
			if (!WaitFor(condition, arg, maxTimeMs, poolingIntervalMs))
			{
				throw new Exception("Time Out: " + TimeSpan.FromMilliseconds(maxTimeMs).ToSpanString());
			}
		}

		public static bool WaitFor(this Func<bool> condition)
		{
			return WaitFor(condition, _defaultMaxTimeMs, _defaultPoolingIntervalMs);
		}

		public static bool WaitFor(this Func<bool> condition, int maxTimeMs)
		{
			return WaitFor(condition, maxTimeMs, _defaultPoolingIntervalMs);
		}

		public static bool WaitFor(this Func<bool> condition, int maxTimeMs = _defaultMaxTimeMs, int poolingIntervalMs = _defaultPoolingIntervalMs)
		{
			return WaitFor<object>(x => condition(), null, maxTimeMs, poolingIntervalMs);
		}

		public static bool WaitFor<T>(this Func<T, bool> condition, T arg, int maxTimeMs, int poolingIntervalMs)
		{
			var sw = Stopwatch.StartNew();
			while (!condition(arg))
			{
				Thread.Sleep(poolingIntervalMs);
				if (!Debugger.IsAttached && sw.Elapsed.TotalMilliseconds > maxTimeMs)
				{
					return false;
				}
			}
			return true;
		}

		[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public static T Attribute<T>(this ICustomAttributeProvider cap) where T : Attribute
		{
			var atrs = cap.GetCustomAttributes(typeof(T), true);
			if (atrs.Length > 0)
			{
				return (T)atrs[0];
			}
			return null;
		}

		public const BindingFlags BindAll = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
		public const BindingFlags BindAllIgnoreCase = BindAll | BindingFlags.IgnoreCase;
		public const BindingFlags BindAllAndStatic = BindAll | BindingFlags.Static;
		public const BindingFlags BindAllAndStaticIgnoreCase = BindAllAndStatic | BindingFlags.IgnoreCase;
		public const BindingFlags BindAllFlat = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
		public const BindingFlags BindAllIgnoreCaseFlat = BindAll | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
		public const BindingFlags BindAllAndStaticFlat = BindAll | BindingFlags.Static | BindingFlags.FlattenHierarchy;
		public const BindingFlags BindAllAndStaticIgnoreCaseFlat = BindAllAndStatic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;

		public const RegexOptions RegexConfig =
			RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture;

		const double _daysInYear = 365.24;

		static readonly Dictionary<Type, object> _mapTypeToDefaultValue = new Dictionary<Type, object>();
		static readonly Random rnd = new Random();
		static string binPath;

		public static bool IsWinCE
		{
			get { return Environment.OSVersion.Platform == PlatformID.WinCE; }
		}

		/// <summary>
		/// convert string with digits very fast to unsigned integer number
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public static int? ToNumber(this string data)
		{
			/*todo: perform checking during enumeration*/
			if (!data.IsNullOrEmpty() && data.ToCharArray().All(x => char.IsDigit(x)))
			{
				int result = 0, j = 1;
				for (var i = data.Length - 1; i >= 0; i--, j *= 10)
				{
					result += j * (int)char./*fucking!*/GetNumericValue/*todo: replace that with code number!*/(data[i]);
				}
				return result;
			}
			return null;
		}

		public static string BinPath
		{
			get
			{
				if (binPath == null)
				{
					binPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
				}
				return binPath;
			}
		}

		public static bool IsNullOrEmpty(this string value)
		{
			return string.IsNullOrEmpty(value);
		}

		public static bool IsNullOrWhitespaces(this string value)
		{
			return string.IsNullOrEmpty(value) ? true : value.Trim().Length == 0;
		}

		// TODO: Create TryEnsureRemoveObject and replace usage with try catch
		[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
			MessageId = "System.GC.Collect")]
		[SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#")]
		public static void EnsureRemoveObject<T>(ref T item) where T : class
		{
			var wr = new WeakReference(item);
			item = null;
			GC.Collect();
			GC.WaitForPendingFinalizers();
			if (wr.IsAlive)
			{
				item = (T)wr.Target;
				throw new ContractException("Can not remove object. Something refers to it.");
			}
		}

		public static string ToSizeString(this decimal size)
		{
			return ((InfoSize)size).ToStringBinarySi();
		}

		[CLSCompliant(false)]
		public static string ToSizeString(this ulong size)
		{
			return ToSizeString((decimal)size);
		}

		public static string ToSizeString(this long size)
		{
			return ToSizeString((decimal)size);
		}

		public static string ToSizeString(this int size)
		{
			return ToSizeString((decimal)size);
		}

		[CLSCompliant(false)]
		public static string ToSizeString(this uint size)
		{
			return ToSizeString((decimal)size);
		}


		//    //public static string UserAppDataPath = "{0}\\{1}".Arg(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),Assembly.GetExecutingAssembly().Attribute<AssemblyProductAttribute>().Product;
		//    //static string userAppDataRegistry;

		////#if !PocketPC
		////    static string commonAppDataPath = 
		////    static string commonAppDataRegistry;
		////#endif

		public static string GetFileNameInBin(string fileNameRelativePath)
		{
			return Path.Combine(BinPath, fileNameRelativePath.Replace('/', '\\'));
		}

		[Conditional("DEBUG")]
		[DebuggerNonUserCode]
		public static void Ensure(this bool value)
		{
			EnsureCore(value, null);
		}

		[Conditional("DEBUG")]
		[DebuggerNonUserCode]
		public static void Ensure(this bool value, string msg)
		{
			EnsureCore(value, msg);
		}

		[DebuggerStepThrough]
		internal static void EnsureCore(bool value, string msg)
		{
			if (!value)
			{
				throw new ContractException("Contract violated" + (string.IsNullOrEmpty(msg) ? null : ": " + msg));
			}
		}

		[Conditional("DEBUG")]
		[DebuggerNonUserCode]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public static void EnsureExists(this object item)
		{
			EnsureCore(Exists(item), "Object does not exists (value==null)");
		}

		[Conditional("DEBUG")]
		[DebuggerNonUserCode]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public static void EnsureReferenceExists(this object item)
		{
			EnsureCore(!ReferenceEquals(null, item), "Object does not exists (reference equals null)");
		}

		[Conditional("DEBUG")]
		[DebuggerNonUserCode]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public static void EnsureExists(this object item, string msg)
		{
			EnsureCore(null != item, "Object does not exists (value==null): " + msg);
		}

		public static string[] SplitToLines(this string value)
		{
			value = value.Replace("\r\n", "\n"); // win => nix
			value = value.Replace("\r", "\n"); // mac => nix
			return value.Split('\n').Where(x => x.Length > 0).ToArray();
		}

		/// <summary>
		/// Format with current culture
		/// </summary>
		/// <param name="format"></param>
		/// <param name="args"> </param>
		/// <returns></returns>
		public static string Arg(this string format, params object[] args)
		{
			return string.Format(CultureInfo.CurrentCulture, format, args)/*.Replace('`', '"')*/;
		}

		/// <summary>
		/// Format with invariant culture
		/// </summary>
		/// <param name="format"></param>
		/// <param name="args"> </param>
		/// <returns></returns>
		public static string ArgI(this string format, params object[] args)
		{
			return string.Format(CultureInfo.InvariantCulture, format, args)/*.Replace('`', '"')*/;
		}

		/// <summary>
		/// Safely universal object existance check
		/// </summary>
		/// <returns>false - for null reference, INullObject, [NullObject], empty IEnumerable, and default values of ValueTypes</returns>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public static bool Exists(this object item)
		{
			if (item != null)
			{
				if (item.GetType().Attribute<NullObjectAttribute>() != null)
				{
					return false;
				}

				var en = item as IEnumerable;
				if (en != null)
				{
					return en.GetEnumerator().MoveNext();
				}
				if (item is ValueType)
				{
					return !item.Equals(DefaultValue(item.GetType()));
				}
				return true;
			}
			return false;
		}

		public static void ForEach<T>(this IEnumerable<T> source, Action<T> act)
		{
			foreach (var item in source)
			{
				act(item);
			}
		}

		static object DefaultValue(Type type)
		{
			object r;
			if (!_mapTypeToDefaultValue.TryGetValue(type, out r))
			{
				_mapTypeToDefaultValue[type] = r = Activator.CreateInstance(type);
			}
			return r;
		}

		public static int Random(this int max)
		{
			if (max == 1 || max == 0)
			{
				return 0;
			}
			return rnd.Next(max);
		}

		public static string ToSpanString(this TimeSpan span)
		{
			var cult = CultureInfo.InvariantCulture;

			var prefix = string.Empty;
			if (span.Ticks == 0)
			{
				return "0 min";
			}
			if (span.Ticks < 0)
			{
				prefix = "-";
				span = span.Negate();
			}
			string s;

			if (span.TotalSeconds < 1)
			{
				s = span.TotalMilliseconds.ToString("0.# ms", cult);
			}
			else if (span.TotalMinutes < 1)
			{
				s = span.TotalSeconds.ToString("0.# sec", cult);
			}
			else if (span.TotalHours < 1)
			{
				s = span.TotalMinutes.ToString("0.# min", cult);
			}
			else if (span.TotalDays < 1)
			{
				s = span.TotalHours.ToString("0.# hr", cult);
			}
			else
			{
				var days = span.TotalDays.ToString("0.# day" + (span.TotalDays >= 2 ? "s" : null), cult);

				if (span.TotalDays > _daysInYear)
				{
					var v = span.TotalDays / _daysInYear;
					s = v.ToString("~0.# year" + (v >= 2 ? "s" : null), cult) + " (" + days + ")";
				}
				else
				{
					s = days;
				}
			}

			return prefix + s;
		}

		[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		[CLSCompliant(false)]
		public static object ConvertTo<T>(this IConvertible inst)
		{
			return ConvertTo(inst, typeof(T));
		}

		[CLSCompliant(false)]
		public static object ConvertTo(this IConvertible inst, Type type)
		{
			if (inst == null)
			{
				throw new ArgumentNullException("inst");
			}
			try
			{
				return inst.ToType(type, CultureInfo.CurrentCulture);
			}
			catch (Exception ex1)
			{
				try
				{
					var method = type.GetMethod("ConvertToThis");
					if (method == null)
					{
						throw new MissingMethodException("Method not found: {0}.ConvertToThis(object)".Arg(type.Name));
					}
					return method.Invoke(null, new[]
					{
						inst
					});
				}
				catch (Exception ex2)
				{
					throw new NotSupportedException(
						"Unable to convert from {0} to {1}.\r\nToType exception: {2}\r\nConvertToThis exception: {3}".Arg(
							inst.GetType().Name, type.Name, ex1.Message, ex2.Message));
				}
			}
		}

		public static string HumanReadableStartCase(object identifier)
		{
			return HumanReadable(identifier, true);
		}

		public static string HumanReadable(object identifier)
		{
			return HumanReadable(identifier, false);
		}

		public static string HumanReadable(object identifier, bool startCase)
		{
			if (identifier == null)
			{
				return "(None)";
			}
			var s = identifier as string ?? identifier.ToString();
			s = s.Replace("__", "*");
			var sb = new StringBuilder();
			for (var i = 0; i < s.Length; i++)
			{
				var c = s[i];
				if (c == '_')
				{
					// ruby_style => Ruby style
					sb.Append(' ');
					if (++i < s.Length)
					{
						sb.Append(wordFirstLetterCaseCorrection(s[i], startCase));
					}
				}
				else if (char.IsUpper(c))
				{
					// camelCase => Camel case
					sb.Append(' ').Append(wordFirstLetterCaseCorrection(c, startCase));
				}
				else
				{
					sb.Append(c);
				}
			}

			var res = sb.ToString().Trim();

			res = char.ToUpper(res[0], CultureInfo.CurrentCulture) + res.Substring(1);

			res = res.Replace("*", "_").Replace("  ", " ");
			return res;
		}

		static char wordFirstLetterCaseCorrection(char c, bool eachWordUppercase)
		{
			return eachWordUppercase
						? char.ToUpper(c, CultureInfo.CurrentCulture)
						: char.ToLower(c, CultureInfo.CurrentCulture);
		}

		static MemberInfo getMember(object obj, string memberName, object[] args)
		{
			obj.EnsureReferenceExists();

			var type = obj as Type ?? obj.GetType();

			var formatargs = new[]
			{
				memberName, type.Name
			};

			var flagsToTry = new[]
			{
				BindAll, BindAllIgnoreCase, BindAllAndStatic, BindAllAndStaticIgnoreCase,
				BindAllFlat, BindAllIgnoreCaseFlat, BindAllAndStaticFlat, BindAllAndStaticIgnoreCaseFlat,
			};

			MemberInfo[] members = null;
			var wasMulti = false;
			foreach (var flags in flagsToTry)
			{
				members = type.GetMember(memberName, flags);
				if (members.Length > 1)
				{
					wasMulti = true;
				}
				else if (members.Length == 1)
				{
					break;
				}

				members = getMembers(members, args);

				//logr("filtered", members);

				if (members.Length == 1)
				{
					break;
				}
				if (members.Length == 0)
				{
					continue;
				}
				if (members.Length > 1)
				{
					Trace.WriteLine("Reflection of '{1}': ambiguous call '{0}'".Arg(formatargs));
				}
			}

			// ReSharper disable PossibleNullReferenceException
			if (members.Length == 1)
			// ReSharper restore PossibleNullReferenceException
			{
				return members.First();
			}
			if (wasMulti)
			{
				throw new ArgumentException("Reflection of '{1}': ambiguous call '{0}'".Arg(formatargs));
			}

			throw new ArgumentException("Reflection of '{1}': there are none '{0}'\r\nTry one of:\r\n".Arg(formatargs)
												 + obj.GetType().GetMembers(BindAllAndStatic).Select(x => x.Name).Join("\r\n"));
		}

		static MemberInfo[] getMembers(MemberInfo[] members, object[] args)
		{
			members = members.Where(x =>
			{
				var mi = x as MethodInfo;
				return mi == null || !mi.IsGenericMethod;
			}).ToArray();

			members = members.Where(x =>
			{
				var mi = x as MethodInfo;
				return mi == null
						 || allowThisParameters(mi.GetParameters().Select(y => y.ParameterType).ToArray(), args, x);
			}).ToArray();

			members = members.Where(x =>
			{
				var pi = x as PropertyInfo;
				return pi == null
						 || allowThisParameters(pi.GetIndexParameters().Select(y => y.ParameterType).ToArray(), args, x);
			}).ToArray();

			if (members.Length > 1)
			{
				members = members.Where(x =>
				{
					var mi = x as MethodInfo;
					return mi == null
							 || exactThisParameters(mi.GetParameters().Select(y => y.ParameterType).ToArray(), args, x);
				}).ToArray();
				members = members.Where(x =>
				{
					var pi = x as PropertyInfo;
					return pi == null
							 || exactThisParameters(pi.GetIndexParameters().Select(y => y.ParameterType).ToArray(), args, x);
				}).ToArray();
			}
			return members;
		}

		static bool exactThisParameters(Type[] argTypes, object[] args, MemberInfo mi)
		{
			var cntDif = args.Length - argTypes.Length;
			if (cntDif == 1 || cntDif == 0)
			{
				for (var i = 0; i < argTypes.Length; i++)
				{
					var argsType = args[i] == null ? null : args[i].GetType();
					// exactly that type
					if (argTypes[i] != argsType)
					{
						return false;
					}
				}

				if (cntDif == 0)
				{
					return true;
				}
				var pi = mi as PropertyInfo;
				if (pi != null)
				{
					if (pi.CanWrite)
					{
						var lastArgType = args.Last() == null ? null : args.Last().GetType();
						if (pi.PropertyType == lastArgType)
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		//	static void logr(object comment, IEnumerable<MemberInfo> state) {
		//		Trace.WriteLine("Reflection Searching " + comment + ".\r\n" + state.Select(x => memberInfoToString(x)).Join("\r\n"));
		//	}

		//	static string memberInfoToString(MemberInfo methodInfo) {
		//		string r;
		//		if (methodInfo is MethodInfo) {
		//			var fi = methodInfo as MethodInfo;
		//			r = "{2}{0} {1}({3})".Arg(typeToString(fi.ReturnType), fi.Name, fi.IsStatic ? "static " : null, fi.GetParameters().Select(x => typeToString(x.ParameterType) + ' ' + x.Name).Join(", "));
		//		} else if (methodInfo is PropertyInfo) {
		//			var fi = methodInfo as PropertyInfo;
		//			r = "{0} {1}".Arg(typeToString(fi.PropertyType), fi.Name);
		//			ParameterInfo[] ip = fi.GetIndexParameters();
		//			if (ip.Length > 0)
		//				r += "[{0}]".Arg(ip.Select(x => typeToString(x.ParameterType) + ' ' + x.Name).Join(", "));
		//		} else if (methodInfo is FieldInfo) {
		//			var fi = methodInfo as FieldInfo;
		//			r = "{2}{0} {1}".Arg(typeToString(fi.FieldType), fi.Name, fi.IsStatic ? "static " : null);
		//		} else return methodInfo.Name;
		//
		//		//r += " in " + methodInfo.ReflectedType.Name;
		//		return "\t" + r;
		//	}

		//	static string typeToString(Type t) {
		//		if (t == typeof (int)) return "int";
		//		else if (t == typeof (uint)) return "uint";
		//		else if (t == typeof (long)) return "long";
		//		else if (t == typeof (ulong)) return "ulong";
		//		else if (t == typeof (short)) return "short";
		//		else if (t == typeof (ushort)) return "ushort";
		//		else if (t == typeof (byte)) return "byte";
		//		else if (t == typeof (sbyte)) return "sbyte";
		//		else if (t == typeof (decimal)) return "decimal";
		//		else if (t == typeof (string)) return "string";
		//		else if (t == typeof (float)) return "float";
		//		else if (t == typeof (double)) return "double";
		//		return t.Name;
		//	}

		static bool allowThisParameters(Type[] argTypes, object[] args, MemberInfo mi)
		{
			var cntDif = args.Length - argTypes.Length;
			if (cntDif == 1 || cntDif == 0)
			{
				for (var i = 0; i < argTypes.Length; i++)
				{
					if (!typeAssignable(argTypes[i], args[i]))
					{
						return false;
					}
				}

				if (cntDif == 0)
				{
					return true;
				}
				var pi = mi as PropertyInfo;
				if (pi != null)
				{
					if (pi.CanWrite)
					{
						if (typeAssignable(pi.PropertyType, args.Last()))
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		static bool typeAssignable(Type to, object from)
		{
			if (from == null)
			{
				// microsoft reflection implementation converts null to default value
				return true;
			}
			var t = from.GetType();
			return to.IsAssignableFrom(t);
		}

		[EditorBrowsable(EditorBrowsableState.Never)]
		public static object Reflect(this object item, string memberName, params object[] arguments)
		{
			if (arguments == null)
			{
				// the only possible ways for that: special parameter passing for stress test this method, or pass null, expecting that it is new object []{ null }
				// btw note that overload with 'object' parameter does not solve this problem
				arguments = new object[] {null};
			}
			// reflect path recursively without arguments
			while (memberName.Contains('.'))
			{
				var i = memberName.IndexOf('.');
				var parentNode = memberName.Substring(0, i);
				memberName = memberName.Substring(i + 1);
				item = item.Reflect(parentNode); // pass through properties, parameterless methods, etc
			}

			var member = getMember(item, memberName, arguments);
			switch (member.MemberType)
			{
				case MemberTypes.Method:
					var mi = (MethodInfo) member;
					var args = arguments;
					if(mi.ContainsGenericParameters)
					{
						var typeArgsLen = mi.GetGenericArguments().Length;
						var typeArgs = args.Take(typeArgsLen).Cast<Type>().ToArray();
						args = args.Skip(typeArgsLen).ToArray();
						mi = mi.MakeGenericMethod(typeArgs);
					}
					return mi.Invoke(item, args);
				case MemberTypes.Property:
					var pi = (PropertyInfo)member;
					if (arguments.Length == pi.GetIndexParameters().Length)
					{
						return pi.GetValue(item, arguments);
					}
					if (arguments.Length == pi.GetIndexParameters().Length + 1)
					{
						pi.SetValue(item, arguments.Last(), arguments.Take(pi.GetIndexParameters().Length).ToArray());
						return null;
					}
					throw newExSetter();
				case MemberTypes.Field:
					var fi = (FieldInfo)member;
					if (arguments.Length == 0)
					{
						return fi.GetValue(item);
					}
					if (arguments.Length == 1)
					{
						fi.SetValue(item, arguments[0]);
						return null;
					}
					throw newExSetter();
				default:
					throw new ArgumentException(
						"Reflection of '{0}': Unknown or not implemented Member Type: {1}".Arg(item.GetType().Name,
																													  member.MemberType));
			}
		}

		static Exception newExSetter()
		{
			return
				new ArgumentException(
					"Reflection: Specify only one object to set as property or field value. For indexers last argument is setter value, other arguments passed as the indexer parameters");
		}

		public static string Join(this IEnumerable<string> parts, string separator)
		{
			return string.Join(separator, parts.ToArray());
		}

		/// <summary>
		/// When your collection implements IEnumerable&lt;A&gt; and IEnumerable&lt;B&gt;, unfortunately it is impossible to use Enumerable.Cast(IEnumerable) to cast items on the fly, because untyped IEnumerable.GetEnumerator() will be used.
		/// So, this method allows explicitly specify source type of a collection, and the wished derrived result type.
		/// </summary>
		/// <typeparam name="TSource"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public static IEnumerable<TResult> Cast<TSource, TResult>(this IEnumerable<TSource> source)
			where TSource : TResult
		{
			foreach (var item in source)
			{
				yield return item;
			}
		}

		[Obsolete("Specify default scale. If you write '15 3h' that mean 15d 3h, unless you specify what is default scale", true)]
		[EditorBrowsable(EditorBrowsableState.Never)]
#if !PocketPC
		[Browsable(false)]
#endif
		public static TimeSpan ToSpan(this string value)
		{
			return ToSpan(value, "d");
		}

		public static TimeSpan ToSpan(this string value, string defaultScale)
		{
			var ts = default(TimeSpan);

			foreach (Match part in Regex.Matches(value, @"(?'n'[\d,.]+)\s*(?'t'[a-zA-Z]+)?"))
			{
				ts += usePart(part, defaultScale);
			}

			return ts;
		}

		[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
		[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		static TimeSpan usePart(Match part, string defaultScale)
		{
			var sn = part.Groups["n"].Value;
			var st = part.Groups["t"].Value;
			var n = double.Parse(sn.Replace(',', '.'), CultureInfo.InvariantCulture);
			if(st.IsNullOrWhitespaces())
			{
				st = defaultScale;
				if (defaultScale.IsNullOrWhitespaces())
				{
					throw new ContractException("Can not parse TimeSpan without scale specifier and without defaultScale: " + part.Value);
				}
			}
			st = st.ToLower(CultureInfo.InvariantCulture);
			switch (st)
			{
				case "ms":
					return TimeSpan.FromMilliseconds(n);
				case "s":
				case "sec":
				case "secs":
					return TimeSpan.FromSeconds(n);
				case "m":
				case "min":
				case "mins":
					return TimeSpan.FromMinutes(n);
				case "h":
				case "hs":
				case "hr":
				case "hrs":
				case "hour":
				case "hours":
					return TimeSpan.FromHours(n);
				case "d":
				case "ds":
				case "day":
				case "days":
					return TimeSpan.FromDays(n);
				case "w":
				case "wk":
				case "wks":
					return TimeSpan.FromDays(7 * n);
				case "mm":
				case "month":
				case "months":
					return TimeSpan.FromDays(_daysInYear * n / 12);
				case "y":
				case "ys":
				case "yr":
				case "yrs":
				case "year":
				case "years":
					return TimeSpan.FromDays(_daysInYear * n);
			}
			throw new FormatException("Unknown TimeSpan part <{0}> where n = <{1}>, type = <{2}> ".Arg(part, sn, st));
		}

		public static string ToStringUI(this DateTime value)
		{
			switch (value.Kind)
			{
				case DateTimeKind.Local:
					break;
				case DateTimeKind.Utc:
					value = value.ToLocalTime();
					break;
				default:
					throw new Exception();
			}

			var now = DateTime.Now;
			var ci = CultureInfo.CurrentCulture;

			var format = "yyyy-MMM-d ddd H:mm";

			if (now.Date == value.Date)
			{
				if (now.Hour == value.Hour)
				{
					format = "\\'mm.s min"; // TODO verify
				}
				else
				{
					format = ci.DateTimeFormat.ShortTimePattern;
				}
			}
			else
			{
				if (now.Year == value.Year)
				{
					format = format.Substring(format.IndexOf("-") + 1);
					if (now.Month == value.Month)
					{
						format = format.Substring(format.IndexOf("-") + 1);
					}
				}
			}

			return value.ToString(format, ci);
		}

		public static string ToStringFull(this DateTime dt)
		{
			string postfix;
			switch (dt.Kind)
			{
				case DateTimeKind.Local:
					postfix = "zzz";
					break;
				case DateTimeKind.Utc:
					postfix = "";
					break;
				default:
					postfix = "???";
					break;
			}
			if (dt == dt.Date)
			{
				return dt.ToString("yyyy-MMM-dd", CultureInfo.InvariantCulture);
			}
			return dt.ToString("yyyy-MMM-d ddd H:mm UTC" + postfix, CultureInfo.InvariantCulture);
		}

		public static T MinWhereExistOrDefault<T>(params T[] args) where T : IComparable<T>
		{
			args = args.Where(x => x.Exists()).ToArray();
			if (args.Length > 0)
			{
				return args.Min();
			}
			return default(T);
		}

		public static T Min<T>(params T[] args) where T : IComparable<T>
		{
			if (args.Length == 2)
			{
				return args[0].CompareTo(args[1]) < 0 ? args[0] : args[1];
			}
			return args.Min();
		}

		public static T WithMax<T>(this IEnumerable<T> col, Func<T, IComparable> selector) where T : class
		{
			var max = col.FirstOrDefault();
			if (max == null)
			{
				throw new ArgumentException(@"Collection empty, or first element is null", "col");
			}
			var maxVal = selector(max);
			foreach (var current in col)
			{
				var currentVal = selector(current);
				if (currentVal.CompareTo(maxVal) > 0)
				{
					max = current;
					maxVal = currentVal;
				}
			}
			return max;
		}

		public static T WithMin<T>(this IEnumerable<T> col, Func<T, IComparable> selector) where T : class
		{
			var min = col.FirstOrDefault();
			if (min == null)
			{
				throw new ArgumentException(@"Collection empty, or first element is null", "col");
			}
			var minVal = selector(min);
			foreach (var current in col)
			{
				var currentVal = selector(current);
				if (currentVal.CompareTo(minVal) < 0)
				{
					min = current;
					minVal = currentVal;
				}
			}
			return min;
		}

		public static T Max<T>(params T[] args) where T : IComparable<T>
		{
			if (args.Length == 2)
			{
				return args[0].CompareTo(args[1]) > 0 ? args[0] : args[1];
			}
			return args.Max();
		}

		public static T UpperLimit<T>(this T item, T limit) where T : IComparable<T>
		{
			return Min(item, limit);
		}

		public static T LowerLimit<T>(this T item, T limit) where T : IComparable<T>
		{
			return Max(item, limit);
		}

		[Conditional("DEBUG")]
		public static void EnsureGCCollected(ref object obj)
		{
			var wr = new WeakReference(obj);
			obj = null;
			EnsureGCCollectedCore(wr);
		}

		[Conditional("DEBUG")]
		public static void EnsureGCCollected<T>(ref T obj) where T : class
		{
			var wr = new WeakReference(obj);
			obj = null;
			EnsureGCCollectedCore(wr);
		}

		[Conditional("DEBUG")]
		public static void EnsureGCCollected(this WeakReference wr)
		{
			EnsureGCCollectedCore(wr);
		}

		[Conditional("DEBUG")]
		public static void EnsureGCCollected(params WeakReference[] wr)
		{
			EnsureGCCollectedCore(wr);
		}

		static void EnsureGCCollectedCore(params WeakReference[] wrs)
		{
			for (int i = 0; i < wrs.Length; i++)
			{
				var wr = wrs[i];

				if (wr.IsAlive)
				{
					if (!WaitFor(() =>
						{
							GC.Collect();
							GC.WaitForPendingFinalizers();
							return !wr.IsAlive;
						}))
					{
						var target = wr.Target;
						if (target != null)
						{
							throw new ContractException("EnsureGCCollected: Failed: " + target.GetType().Name + " hash=" + target.GetHashCode() + " ToString=" + target);
						}
					}
				}
			}
		}

		public static long ParseSizeString(string str)
		{
			return (InfoSize)str;
		}

		/// <summary>
		/// Is it event about specified property?
		/// </summary>
		public static bool Is(this PropertyChangedEventArgs e, string name)
		{
			return string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == name;
		}
	}
}